package com.lhczf.lucenedb.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.jsoniter.JsonIterator;
import com.jsoniter.any.Any;
import com.lhczf.lucenedb.bean.SystemConfig;
import com.lhczf.lucenedb.consumer.AbstractDataConsumer;
import com.lhczf.lucenedb.consumer.WrapThreadFactory;
import com.lhczf.lucenedb.extend.scorer.EmptySimilarity;
import com.lhczf.lucenedb.production.AbstractDataProduction;
import com.lhczf.lucenedb.util.BeanUtil;
import com.lhczf.lucenedb.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.facet.FacetField;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MMapDirectory;
import org.apache.lucene.util.BytesRef;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ThreadFactory;

/**
 * @author: 刘华春
 * @date: 2019/2/6
 */
@Component
@Slf4j
public class LuceneDbServer {

    private static final Map<String, Boolean> TAXO_INIT_FLAG_MAP = new HashMap<>(32);
    private static final Map<String, Boolean> ALL_INDEX_INIT_FLAG_MAP = new HashMap<>(16);
    /**
     * 存储每个索引用了多少个线程来处理
     */
    private static final Map<String, Integer> EVERY_INDEX_DEAL_THREAD_NUM = new HashMap<>(16);
    private static final int THE_GROUP_NUM_IN_DIRECTORY = 2;
    private static final String LUCENE_TEXT_DATA = "text";
    private static final String LUCENE_STRING_DATA = "text";
    private static final String LUCENE_DOUBLE_DATA = "double";
    private static final String LUCENE_INTEGER_DATA = "integer";

    public Map<String, Integer> getEveryIndexDealThreadNum() {
        return EVERY_INDEX_DEAL_THREAD_NUM;
    }

    public void beginDataProduction(SystemConfig systemConfig) {
        String dealClass = systemConfig.getDataProductionClass();
        if (dealClass == null || dealClass.isEmpty()) {
            log.warn("没有配置生产者类。");
            return;
        }
        String[] classArray = dealClass.split(",");
        for (String classStr : classArray) {
            Object object = null;
            try {
                Class production = Class.forName(classStr);
                object = production.newInstance();
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                log.error("", e);
            }
            if (object == null) {
                continue;
            }
            AbstractDataProduction dataProduction = (AbstractDataProduction) object;
            int threadNum = dataProduction.runtimeThreadNum();
            log.info("【{}】生产者用了【{}】个线程运行。", dataProduction.configIndexName(), dataProduction.runtimeThreadNum());

            for (int i = 0; i < threadNum; i++) {
                String threadNamePrefix = dataProduction.configIndexName() + "_production_";
                ThreadFactory factory = new WrapThreadFactory(threadNamePrefix + i);
                factory.newThread(dataProduction).start();
            }
        }
    }

    public void beginDataConsumer(SystemConfig systemConfig) {
        String className = systemConfig.getDataConsumerClass();
        if (className == null || className.isEmpty()) {
            log.error("消费者类的配置不正确，请检查。");
            return;
        }
        String[] classArray = className.split(",");
        for (String classStr : classArray) {
            Class classObject = loadClass(classStr);
            if (classObject == null) {
                continue;
            }
            AbstractDataConsumer dataConsumer = getDataConsumer(classObject);
            if (dataConsumer == null) {
                continue;
            }
            configTaxoWriter(systemConfig, dataConsumer);
            int threadNum = dataConsumer.runtimeThreadNum();
            log.info("【{}】消费者用了【{}】个线程运行。", dataConsumer.configIndexName(), dataConsumer.runtimeThreadNum());
            startDataConsumer(dataConsumer, 0);
            for (int i = 1; i < threadNum; i++) {
                AbstractDataConsumer consumer = getDataConsumer(classObject);
                if (consumer != null) {
                    startDataConsumer(consumer, i);
                }
            }
            EVERY_INDEX_DEAL_THREAD_NUM.put(dataConsumer.configIndexName(), dataConsumer.runtimeThreadNum());
        }
    }

    private void configTaxoWriter(SystemConfig systemConfig, AbstractDataConsumer dataConsumer) {
        if (dataConsumer.hasConfigTaxoWriter()) {
            DirectoryTaxonomyWriter txo = createTaxoWriter(systemConfig.getIndexData(), dataConsumer.configIndexName());
            if (txo != null) {
                dataConsumer.configTaxoIndexWriter(txo);
            }
        }
    }

    private AbstractDataConsumer getDataConsumer(Class classObject) {
        AbstractDataConsumer dataConsumer = null;
        try {
            dataConsumer = (AbstractDataConsumer) classObject.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            log.error("", e);
        }
        return dataConsumer;
    }

    private Class loadClass(String classStr) {
        Class classObject = null;
        try {
            classObject = Class.forName(classStr);
        } catch (ClassNotFoundException e) {
            log.error("", e);
        }
        return classObject;
    }

    private void startDataConsumer(AbstractDataConsumer dataConsumer, int i) {
        dataConsumer.setId(i);
        ThreadFactory factory = new WrapThreadFactory(dataConsumer.configIndexName() + "_luceneDB_consumer_" + i);
        factory.newThread(dataConsumer).start();
    }

    public void init(SystemConfig systemConfig) {
        createDir(systemConfig);
    }

    private void createDir(SystemConfig systemConfig) {
        String dir = systemConfig.getIndexData() + File.separator + SystemConfig.ALL_INDEXS_DIR + File.separator;
        String indexDir = dir + SystemConfig.TAXONOMY_DIR_NAME;
        File dataDir = new File(indexDir);
        FileUtil.createDir(dataDir);
        log.info("分类索引的存储根目录为：{}", dataDir.getAbsolutePath());

        dataDir = new File(dir + SystemConfig.INDEX_DIR_NAME);
        FileUtil.createDir(dataDir);
        log.info("索引数据的存储根目录为： {}", dataDir.getAbsolutePath());

        dataDir = new File(systemConfig.getBackupData());
        FileUtil.createDir(dataDir);
        log.info("备份数据的存储根目录为： {}", dataDir.getAbsolutePath());
    }

    private StringBuilder makeStringBuilder(String indexData) {
        return new StringBuilder(indexData);
    }

    public IndexWriter createIndexWriter(String indexDir) {
        Directory directory = makeDirectory(indexDir);
        if (directory == null) {
            return null;
        }
        Analyzer analyzer = getAnalyzer();
        IndexWriterConfig config = getIndexWriterConfig(analyzer);
        IndexWriter indexWriter;
        try {
            indexWriter = new IndexWriter(directory, config);
        } catch (IOException e) {
            log.error("", e);
            return null;
        }
        return indexWriter;
    }

    public synchronized IndexWriter createIndexWriter(String indexDir, String indexName) {
        Boolean create = ALL_INDEX_INIT_FLAG_MAP.get(indexName);
        if (create != null && create) {
            return null;
        }
        IndexWriter indexWriter = createIndexWriter(indexDir);
        if (indexWriter != null) {
            ALL_INDEX_INIT_FLAG_MAP.putIfAbsent(indexName, true);
        }
        return indexWriter;
    }

    public Directory makeDirectory(String indexDir) {
        Directory directory;
        try {
            directory = MMapDirectory.open(Paths.get(indexDir));
        } catch (IOException e) {
            log.error("", e);
            return null;
        }
        return directory;
    }

    private IndexWriterConfig getIndexWriterConfig(Analyzer analyzer) {
        SystemConfig systemConfig = BeanUtil.getInstance().getBean(SystemConfig.class);
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
        config.setRAMBufferSizeMB(systemConfig.getLuceneRam());
        config.setSimilarity(new EmptySimilarity());
        return config;
    }

    private Analyzer getAnalyzer() {
        return new SmartChineseAnalyzer();
    }

    public synchronized DirectoryTaxonomyWriter createTaxoWriter(String indexDir, String indexName) {
        if (TAXO_INIT_FLAG_MAP.get(indexName) != null) {
            return null;
        }
        StringBuilder sb = makeStringBuilder(indexDir);
        sb.append(File.separator).append(SystemConfig.ALL_INDEXS_DIR)
                .append(File.separator).append(SystemConfig.TAXONOMY_DIR_NAME).append(File.separator).append(indexName);
        File file = new File(sb.toString());
        FileUtil.createDir(file);
        try {
            //打开的writer在这里不需要关闭，打开之后在整个软件启动期间都需要使用
            Directory directory = MMapDirectory.open(file.toPath());
            DirectoryTaxonomyWriter writer = new DirectoryTaxonomyWriter(directory);
            writer.commit();
            TAXO_INIT_FLAG_MAP.putIfAbsent(indexName, true);
            return writer;
        } catch (IOException e) {
            log.error("", e);
        }
        return null;
    }

    public List<Document> dealDataByJson(String data, Map<String, Object> indexConfig) {
        List<Document> documents = new ArrayList<>();
        List<Any> anies = new ArrayList<>();
        fileAnalysis(data, anies);
        Object configObject = indexConfig.get(SystemConfig.INDEX_CONFIG_KEY);
        boolean indexStore = true;
        if (configObject instanceof JSONObject) {
            indexStore = (boolean) ((JSONObject) configObject).get("store");
        }
        JSONObject properties = (JSONObject) indexConfig.get(SystemConfig.INDEX_PROPERTIES_KEY);
        for (Any any : anies) {
            Document doc = new Document();
            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                Object config = entry.getValue();
                String fieldName = entry.getKey();
                String value = any.get(fieldName).toString();
                createDocs(doc, fieldName, value, indexStore, config);
            }
            documents.add(doc);
        }
        return documents;
    }

    private void createDocs(Document doc, String name, String value, boolean indexStore, Object config) {
        String fieldType = ((JSONObject) config).getString("type");
        Field field = null;
        if (LUCENE_TEXT_DATA.equalsIgnoreCase(fieldType)) {
            if (indexStore) {
                field = new TextField(name, value, Field.Store.YES);
            } else {
                field = new TextField(name, value, Field.Store.NO);
            }
            doc.add(field);
        }
        if (LUCENE_STRING_DATA.equalsIgnoreCase(fieldType)) {
            if (indexStore) {
                field = new StringField(name, value, Field.Store.YES);
            } else {
                field = new StringField(name, value, Field.Store.NO);
            }
            doc.add(field);
            makeOtherField(doc, name, value, (JSONObject) config);
        }
        if (LUCENE_DOUBLE_DATA.equalsIgnoreCase(fieldType)) {
            if (indexStore) {
                field = new DoublePoint(name, Double.parseDouble(value));
            }
            doc.add(field);
            makeOtherField(doc, name, value, (JSONObject) config);
        }
        if (LUCENE_INTEGER_DATA.equalsIgnoreCase(fieldType)) {
            if (indexStore) {
                field = new IntPoint(name, Integer.parseInt(value));
            }
            doc.add(field);
            makeOtherField(doc, name, value, (JSONObject) config);
        }
    }

    private void makeOtherField(Document doc, String name, String value, JSONObject config) {
        Object object = config.get("facet_field");
        if (object != null && Boolean.parseBoolean(object.toString())) {
            doc.add(new FacetField(name, value));
        }
        object = config.get("doc_value");
        if (object != null && Boolean.parseBoolean(object.toString())) {
            doc.add(new SortedDocValuesField(name, new BytesRef(value)));
        }
    }

    public Map<String, Object> mappingConfigInfo(String configPath) {
        String configInfo = FileUtil.readAllContext(configPath);
        Map<String, Object> configInfoMap = new HashMap<>(2);

        TypeReference<LinkedHashMap<String, String>> typeReference = new TypeReference<LinkedHashMap<String, String>>() {
        };
        LinkedHashMap<String, String> jsonMap = JSON.parseObject(configInfo, typeReference);
        if (jsonMap == null) {
            return configInfoMap;
        }
        for (Map.Entry<String, String> entry : jsonMap.entrySet()) {
            configInfoMap.put(SystemConfig.INDEX_NAME_KEY, entry.getKey());
            JSONObject value = JSON.parseObject(entry.getValue());
            Object config = value.get(SystemConfig.INDEX_CONFIG_KEY);
            configInfoMap.put(SystemConfig.INDEX_CONFIG_KEY, config);
            Object properties = value.get(SystemConfig.INDEX_PROPERTIES_KEY);
            configInfoMap.put(SystemConfig.INDEX_PROPERTIES_KEY, properties);
        }
        return configInfoMap;
    }

    private void fileAnalysis(String data, List<Any> anies) {
        File file = new File(data);
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(bis, StandardCharsets.UTF_8), 1024 * 1024)) {
                while (in.ready()) {
                    String content = in.readLine().trim();
                    Any docAny = deserializeFile(content);
                    if (docAny != null) {
                        anies.add(docAny);
                    }
                }
            }
        } catch (FileNotFoundException e) {
            log.error("文件不存在：{}", file.getAbsolutePath());
        } catch (IOException e) {
            log.error("文件操作过程中出现了IO异常。", e);
        }
    }

    private Any deserializeFile(String content) {
        Any any;
        try {
            any = JsonIterator.deserialize(content);
            if (any == null) {
                return null;
            }
        } catch (Exception e) {
            log.error("在转化json的过程中出现异常，文件中的位置是第{}行，语句为：{}", content, e);
            return null;
        }
        return any;
    }

    public void closeWriter(IndexWriter indexWriter) {
        try {
            indexWriter.close();
            log.info("成功关闭indexWritr：{}", findIndexPath(indexWriter.getDirectory()));
        } catch (IOException e) {
            log.error("关闭indexWritr失败：{}", findIndexPath(indexWriter.getDirectory()), e);
        }
    }

    public String findIndexPath(Directory directory) {
        String string = directory.toString();
        String[] values = string.split("MMapDirectory@|NIOFSDirectory@");
        if (values.length == THE_GROUP_NUM_IN_DIRECTORY) {
            return values[1].split(" ")[0];
        } else {
            log.warn("通过分割字符串获取需要备份的索引数据目录异常，目录：{}", string);
        }
        return null;
    }
}
